package mulegame;

import java.awt.Color;
import java.util.Random;
import java.util.Scanner;
/**
 * sets number of players keeps track of turns and turn time
 * 
 * @author AREA 29
 * 
 */

public class GameEngine {
	private Map map;
	private GameConfig config;
	private Store store;
	private Player[] players;
	private int roundCount = 1;
	private int turnCount = 0;
	private int landCost = 0;
	private Random r;

    /**
     * creates new GameConfig object   
     */
   public GameEngine(){
//        Logger.println("GameEngine created");
        config = new GameConfig();
        store = null;
        r = new Random();
   }
   /**
    * GameConfig object calls setNumPlayers to set number of players based on parameter
    *
    *@param num number of players
    */
   public void setNumPlayers(int num){
        config.setNumPlayers(num);
        players = new Player[num];
   }
   
   /**
    * GameConfig sets difficulty of game based on parameter diff
    * 
    * @param diff difficulty level
    */
   public void setDifficulty(GameConfig.Difficulty diff){
        config.setDifficulty(diff);
        store = new Store(diff);
   }
   /**
    * Makes a new Map object based on paramter mapType
    * 
    * @param mapType map type as an integer
    */
   public void makeMap(int mapType){
        map = new Map(mapType);   
   }
   /**
    * returns the map object 
    * 
    * @return map returns the map
    */
   public Map getMap(){
        return map;   
   }
   /**
    * returns number of players
    * 
    * @return config.getNumPlayers(); returns number of players
    */
   
   public int getNumPlayers(){
        return config.getNumPlayers();   
   }
   /**
    * returns calculated current time obtained from calcCurrentTurnTime method
    * 
    * @return calcCurrentTurnTime(); returns current players turn time
    */
   public int getPlayerTurnTime(int player){
        return calcCurrentTurnTime(player);    
   }
   /**
    * Calculates the current players remaining turn time
    * 
    * @param currPlayer current player
    * @return 45*1000 returns turn time based on how much food player has 
    */
   private int calcCurrentTurnTime(int currPlayer){
       int food = players[currPlayer].getFood();
       int ret;
       if(food >= config.getRoundFood(roundCount)){
    	   
            ret = 50*1000; //food at or above required ammount, so turn time is 50 secs   
       }else{
           if(food > 0){
                ret = 30*1000; //partial shortage, turn time is 30 secs   
           }
           else{
                ret = 5*1000; //severe shortage, turn time is 5 secs   
           }
       }
       return ret;
   }
   
   /**
    * increments the turnCount and resets it once everyone has gone
    * @param turn turn as a number
    */
    public void setTurn(int turn){
        turnCount = turn;   
    }
    
    public int getTurn(){
        return turnCount;   
    }
    
   /**
    * increments roundCount by 1
    */
   public void incrRound(){
        doProduction(); //calculate the production for the previous round and deduct energy
        deductRoundFood();//deduct the food from the previous round
        roundCount++;   
        Logger.println("Round: " + new Integer(roundCount).toString());
        sortPlayers();
        landCost = roundCount <= 2 ? 0 : 300;
   }
   /**
    * sorts the players based on their score and changes their place in the array based on that
    */
   public void sortPlayers(){
       //bubble sort because there will be at most 4 entries in the player list 
       boolean sorting = true;
       while(sorting){
           sorting = false;
           for(int i = 0; i < players.length - 1; i++){
               if(players[i].getScore() > players[i+1].getScore()){
                   sorting = true;
                   Player temp = players[i];
                   players[i] = players[i+1];
                   players[i+1] = temp;
               }
           }
       }
   }
   /**
    * deducts the food of the players by round
    */
   public void deductRoundFood(){
       for(Player p: players){
            p.deductFood(config.getRoundFood(roundCount));   
       }
   }
   /**
    * returns roundCount
    * 
    * @return roudCount returns how many rounds there have been
    */
   public int getRound(){
        return roundCount;   
   }
   /**
    * returns landCost
    * @return landCost returns the cost of a plot of land
    */
   public int getLandCost(){
        return landCost;   
   }
   
   public int getFoodCost(){
	   return store.getFoodCost();
   }
   
   public int getEnergyCost(){
	   return store.getEnergyCost();
   }
   
   public int getOreCost(){
	   return store.getOreCost();
   }
   
   public GameConfig.Difficulty getDifficulty() {
	   return config.getDifficulty();
   }
   
   /**
    * makes a player object of specified name, race, and color
    * 
    * @param name name of player
    * @param race race of player
    * @param col color of player
    */
   public void makePlayer(String name, Player.Race race, Color col){
        players[turnCount] = new Player(name, race, col, this.getDifficulty());
   }
   /**
    *gets player color and returns color
    * 
    * @param p  index of player in array 
    * @return players[p-1].getColor() returns player's color
    */
   public Color getPlayerColor(int p){
	   return players[p-1].getColor();
   }
   /**
    * gets player money and returns value
    * 
    * @param p index of player in array
    * @return players[p-1].getMoney() returns how much money player has 
    */
   public int getPlayerMoney(int p){
	   return players[p-1].getMoney();
   }
   /**
    * sets the current players money with amount based on parameter
    * @param m amount of money to give to current player's turn
    */
   public void setCurrPlayerMoney(int m){
	   players[turnCount].setMoney(m);
   }
   
   /**
    * gets the player's name who it currently is the turn of 
    * 
    * @return players[turnCount].getName() returns the current player's name
    */
   public String getCurrentPlayerName(){
        return players[turnCount].getName();   
   }
   /**
    * returns the current player
    * @return players[turnCount] returns the current player
    */
   public Player getCurrPlayer(){
	   return players[turnCount];
   }
   /**
    * returns all the players in an array
    * @return players returns all the the players
    */
   public Player[] getPlayers(){
        return players;   
   }
   
   /**
    * returns the players at specified index in array
    * @return players return the player at specified index in array
    */
   public Player getPlayer(int i){
        return players[i];   
   }
   
   public Store getStore(){
        return store;   
   }
   /**
    * Checks if a player whose current turn it is can afford a certain cost. 
    * 
    * @param cost cost of the object
    * @return players[turnCount].canAfford(cost) returns whether player can afford
    */
   public boolean playerCanAfford(int cost){
        return players[turnCount].canAfford(cost);   
   }
   /**
    * checks if a player can buy land 
    * 
    * @return playerCanAfford(landCost) returns if player can buy land
    */ 
   public boolean playerCanBuyLand(){
        return playerCanAfford(landCost);   
   }
   /**
    * If the player can buy land deduct the cost of the land from the current player and set tile to owned by 
    * player
    * 
    * @param t the tile being bought
    * @return true returns true if player has successfully bought the tile
    */
   public boolean buyLand(Tile t){
       if(playerCanBuyLand()){
           //deduct the cost from the current player
           players[turnCount].charge(landCost);
           
           //set the tile owned by the current player
           t.setOwned(players[turnCount]);
           return true;
       }else{
           return false;
       }
   }
   /**
    * store sells mule to current player and gives to player if player can buy it
    * @param m mule
    * @return true if successfully bought
    */
   public boolean buyMule(Mule m){
       boolean ret = store.sellMuleTo(players[turnCount], m); 
       if(ret){
            players[turnCount].giveMule(m);     
       }
       return ret;
   }
   /**
    * returns cost of a mule from store
    * @param m mule
    * @return store.getMuleCost(m)
    */
   public int getMuleCost(Mule m){
	   return store.getMuleCost(m);
   }
   /**
    * Store can sell food, energy, or ore to current player. Otherwise returns an error message. 
    * @param type type of item being sold
    * @param quantity amount being bought
    * @return true if purchase successful
    */
   public boolean storeSellTo(Store.ItemType type, int quantity){
       boolean ret = false;
       switch(type){
       case FOOD:
           ret = store.sellFoodTo(players[turnCount], quantity); 
           if(ret){
               players[turnCount].giveFood(quantity);
           }
           break;
       case ENERGY:
           ret = store.sellEnergyTo(players[turnCount], quantity);
           if(ret){
                players[turnCount].giveEnergy(quantity);   
           }
           break;
       case ORE:
           ret = store.sellOreTo(players[turnCount], quantity);
           if(ret){
               players[turnCount].giveOre(quantity);
           }
           break;
       default:
           //shouldn't happen, so log error
//           Logger.println("storeSellTo: unknown item type");
           break;
       }
       return ret;
   }
   /**
    * Store buys food from current player. Can buy food, energy and ore. Otherwise returns an error message. 
    * @param item item in the Store
    * @param quantity quantity being bought
    * @return true if item is bought
    */
   public boolean storeBuyFrom(Store.ItemType item, int quantity){
        boolean ret = false;
        switch(item){
        case FOOD:
            ret = store.buyFoodFrom(players[turnCount], quantity);
            break;
        case ENERGY:
            ret = store.buyEnergyFrom(players[turnCount], quantity);
            break;
        case ORE:
            ret = store.buyOreFrom(players[turnCount], quantity);
            break;
        default:
            Logger.println("storeBuyFrom: unknown item type");
            break;
        }
        return ret;
   }
   
   /**
    * returns player name 
    * 
    * @param p index of the player in the array holding all players
    * @return players[p-1].getName() returns player's name
    */
   public String getPlayerName(int p){
	   return players[p-1].getName();
   }
   /**
    * returns player score
    * 
    * @param p index of the player in the array holding all players
    * @return players[p-1].getScore() returns player's score
    */
   public int getPlayerScore(int p){
	   return players[p-1].getScore();
   }
   /**
    * returns all of the current player's resources: money, energy, ore and food as an array
    * @return resources array
    */
   public int[] getCurrPlayerResources(){
	   int[] resources = new int[4];
	   resources[0] = players[turnCount].getMoney();
	   resources[1] = players[turnCount].getEnergy();
	   resources[2] = players[turnCount].getOre();
	   resources[3] = players[turnCount].getFood();
	   return resources;
   }
   
   /**
    * calculates how much money to give player partially randomnly and partially based on how much time the player
    * has left
    * @param timeleft how much time the player has left at the end of their turn
    */
   public int gamble(int timeleft){
        int bonus = 0;
        int timeBonusLimit = 0;
        if(timeleft >= 37 && timeleft <= 50){
            timeBonusLimit = 201;
        } else if(timeleft >= 25 && timeleft <= 37){
            timeBonusLimit = 151;
        } else if (timeleft >= 12 && timeleft <= 25){
            timeBonusLimit = 101;
        } else if(timeleft >= 0 || timeleft <= 12){
            timeBonusLimit = 51;
        }
        int timeBonus = r.nextInt(timeBonusLimit);
        
        int roundBonus = config.getRoundBonus(roundCount);
        
        //compute bonus and limit bonus to $250
        bonus = (timeBonus + roundBonus) > 250 ? 250 : (timeBonus + roundBonus);
        
        players[turnCount].pay(bonus);
        return bonus;
   }
   
	public int getRoundRandomEventFactor(){
		return config.getRoundRandomEventFactor(roundCount);
	}
	/**
	 * Controls random event happening. 27% chance that a random event will happen and player in last place can't
	 * get a bad event. Randomly assigns a random event to player. 
	 * @return c number of event that happens
	 */
	public int randomEvent() {
		Random r = new Random();
		int p = r.nextInt(100);
		int c = 7;
		int m = config.getRoundRandomEventFactor(roundCount);
		if (p < 27) {
			
			if((turnCount - 1) != 0){
				c = r.nextInt(7);
			} else {
				c = r.nextInt(4);
			}
			
			switch (c) {
			case 0:
				Logger.println("EVENT 1 HAPPENED of gaining 3 food and 2 energy");
				players[turnCount].giveFood(3);
				players[turnCount].giveEnergy(2);
				break;
			case 1:
				Logger.println("EVENT 2 HAPPENED of gaining 2 ore");
				players[turnCount].giveOre(2);
				break;
			case 2:
				Logger.println("EVENT 3 HAPPENED of gaining 8*m(" + Integer.toString(8*m) + ") money");
				players[turnCount].pay(8 * m);
				break;
			case 3:
				Logger.println("EVENT 4 HAPPENED of gaining 2*m(" + Integer.toString(2*m) + ") money");
				players[turnCount].pay(2*m);
				break;
			case 4:
				Logger.println("EVENT 5 HAPPENED of being charged 4*m(" + Integer.toString(4*m) + ") money");
				players[turnCount].charge(4*m);
				break;
			case 5:
				Logger.println("EVENT 6 HAPPENED of losing half of food");
				players[turnCount].deductFood(players[turnCount].getFood()/2);
				break;
			case 6:
				Logger.println("EVENT 7 HAPPENED of being charged 6*m(" + Integer.toString(6*m) + ") money");
				players[turnCount].charge(6*m);
				break;
			}
		}
		return c;
	}
	/**
	 * Controls production in turns. If tile is owned and has a food mule on it, the player get food and uses energy.
	 * In case of energy mule, player gets energy. In case of ore mule, player gets ore and uses energy. If none of 
	 * these then logger prints an error statement. 
	 */
   public void doProduction(){
       Player player;
       
        Tile[][] tiles = map.getTileGrid();
        for(Tile[] row : tiles){
            for(Tile t : row){
                if(t.isOwned()){
                    if(t.hasMule()){
                        player = t.getOwner();
                        switch(t.getMule()){
                        case FOOD:
                            if(player.getEnergy() > 0){
                                player.giveFood(t.getFoodProd());
                                player.takeEnergy(1);
                            }
                            break;
                        case ENERGY:
                            player.giveEnergy(t.getEnergyProd());
                            break;
                        case ORE:
                            if(player.getEnergy() > 0){
                                player.giveOre(t.getOreProd());
                                player.takeEnergy(1);
                            }
                            break;
                        default:
                            Logger.println("doProduction: unknown mule type");
                            break;
                        	}                        
                        }
                    }
                }
            }   
        }
        /**
         * returns all player names in a string
         * @return ret
         */
        public String saveGame(){
            String ret = "";
            for(Player p : players){
                ret += "[Player]\n";
                ret += p.toString();
            }
            ret += "[Map]\n";
            ret += map.toString();
            ret += "[Game State]\n";
            ret += "Round : " + Integer.toString(roundCount) + "\n";
            ret += "Turn : " + Integer.toString(turnCount) + "\n";
            ret += "[Store]\n";
            ret += store.toString();
            ret += "[Game Config]\n";
            ret += config.toString() + "\n";
            return ret;
        }
        /**
         * lets player know the game is loading
         */
        public void loadGame(Scanner game){
            String currLine;
            Player[] tempPlayers = new Player[4];
            String[] mapDef = null;
            int playerIndex = 0;
            game.useDelimiter("\n");
            while(game.hasNext()){
                currLine = game.next();
                if(currLine.startsWith("[Player]")){
                    String[] def = new String[7]; 
                    for(int i = 0; i < 7; i++){     //read the 7 lines of player data
                        def[i] = game.next();
                    }
                    tempPlayers[playerIndex++] = new Player(def);
                }
                if(currLine.startsWith("[Map]")){
                    currLine = game.next();
                    String[] args = currLine.split("\\s:\\s");
                    if(args[0].equals("Lines")){
                        mapDef = new String[Integer.parseInt(args[1])];
                        for(int i = 0; i < mapDef.length; i++){
                            mapDef[i] = game.next();   
                        }
                    } else {
                        Logger.println("cannot find number of lines in map definition");
                    }
                }
                if(currLine.startsWith("[Game State]")){
                    String[] args;
                    for(int i = 0; i < 2; i++){     //read the two lines of game state data
                        args = game.next().split("\\s:\\s");
                        if(args[0].equals("Round")){
                            roundCount = Integer.parseInt(args[1]);       
                        }
                        if(args[0].equals("Turn")){
                            turnCount = Integer.parseInt(args[1]);       
                        }
                    }
                }
                if(currLine.startsWith("[Store]")){
                    String[] storeDef = new String[6];
                    for(int i = 0; i < storeDef.length; i++){
                        storeDef[i] = game.next();   
                    }
                    store = new Store(storeDef);
                           
                }
                if(currLine.startsWith("[Game Config]")){
                    String[] def = new String[2];
                    for(int i = 0; i < 2; i++){
                        def[i] = game.next();   
                    }
                    config = new GameConfig(def);
                }
            }
            setNumPlayers(playerIndex);
            for(int i = 0; i < playerIndex; i++){
                players[i] = tempPlayers[i];
            }
            map = new Map(mapDef, this);  
            for(Player p: players){
                Logger.println(p.toString());   
            }
            Logger.println(map.toString());
            Logger.println(store.toString());
        }
        
        public Player lookupPlayerByName(String name){
            for(Player p: players){
                if(p.getName().equals(name)){
                    return p;   
                }
            }
            Logger.println("Player " + name + " not found");
            return null;
        }
}
   
